package lib.base;

import com.google.gdata.client.calendar.*;
import com.google.gdata.data.*;
import com.google.gdata.data.acl.AclEntry;
import com.google.gdata.data.acl.AclFeed;
import com.google.gdata.data.acl.AclNamespace;
import com.google.gdata.data.acl.AclRole;
import com.google.gdata.data.acl.AclScope;
import com.google.gdata.data.calendar.*;
import com.google.gdata.data.extensions.*;
import com.google.gdata.util.*;

import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.io.*;

/**
 * The Class EgoCalendar.
 * <p>
 * Some basic methods for testing the API
 * 
 * @author te
 * @author jg
 * 
 * TODO removing all test-methods or marking them as tests
 */
public class EgoCalendar 
{
	
	/** The my service. 
	 * <p>
	 * A preconfigured Service for access to the Google Calendar API
	 * 
	 * @see <http://code.google.com/intl/de-DE/apis/gdata/javadoc/com/google/gdata/client/calendar/CalendarService.html
	 */
	protected CalendarService myService;
	
	/** The meta feed. 
	 * <p>
	 * The metafeed is a private, read-only feed that contains an entry for each calendar that a user has access to. The URL for the metafeed is:
	 * <code>http://www.google.com/calendar/feeds/default</code>
	 * 
	 * @see <http://code.google.com/intl/de-DE/apis/calendar/docs/2.0/reference.html#Calendar_feeds 
	 */
	public URL metaFeed;	
	
	/** The allcalendars feed.
	 * <p> 
	 * The allcalendars feed is a private read/write feed that is used for managing subscriptions and personalization settings of a user's calendars. Unlike the metafeed, the allcalendars feed is writable, so calendar entries can be inserted and deleted, which is equivalent to subscribing and unsubscribing to existing calendars. Calendar entries in the allcalendars feed can also be updated, but since a user may not own all the calendars in this feed, only personalization settings can be updated with this feed.
	 * The URL for the allcalendars feeds is:
	 * <code>http://www.google.com/calendar/feeds/default/allcalendars/full</code>
	 * 
	 * @see <http://code.google.com/intl/de-DE/apis/calendar/docs/2.0/reference.html#Calendar_feeds
	 */
	public URL allcalendarsFeed;
	
	
	/** The event feed. 
	 *<p>
	 * To request a particular representation of an event feed, you specify both a visibility value and a projection value in the URI that you send to Calendar.
	 * For example, the following URI is for a feed of calendar events with private visibility and full projection:
	 * <code>http://www.google.com/calendar/feeds/jo@gmail.com/private/full</code>
	 * 
	 * @see <http://code.google.com/intl/de-DE/apis/calendar/docs/2.0/reference.html#Calendar_feeds
	 */
	public URL eventFeed;

	// temporary save allCalendars
	protected List<CalendarEntry> _allCalendars = null;
	
	private String username;
	private String password;
	
	/**
	 * Instantiates a new ego calendar.
	 *
	 * @params username - the google systemAccount
	 * @params password - the google systemAccount
	 * 
	 * @throws AuthenticationException the authentication exception
	 * @throws MalformedURLException the malformed url exception
	 * 
	 * @return void
	 */
	public EgoCalendar(String username_, String password_) throws AuthenticationException, MalformedURLException
	{
		username = username_;
		password = password_;
		login();
		this.metaFeed = new URL("http://www.google.com/calendar/feeds/default");
		this.allcalendarsFeed = new URL("http://www.google.com/calendar/feeds/default/owncalendars/full");
		this.eventFeed = new URL("http://www.google.com/calendar/feeds/gr1eipb0opd55p8tl9u7c3t878%40group.calendar.google.com/private/full"); //Testkalender
	}
	
	private void login() throws AuthenticationException
	{
		this.myService = new CalendarService("EGOTEC-icebeer-0.1x");
		this.myService.setUserCredentials(username, password);
	}
	
	/**
	 * checks whether the google authentification works
	 * 
	 * @param username
	 * @param password
	 * @return boolean
	 */
	static public boolean checkCredentials(String username, String password)
	{
		try {
			CalendarService s = new CalendarService("EGOTEC-icebeer-0.1x");
			s.setUserCredentials(username, password);
		} catch (Exception e)
		{
			return false;
		}
		return true;
	}
	
	/**
	 * Get all calendars.
	 * 
	 * @param String cid Customer ID
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws ServiceException the service exception
	 * 
	 * @return void
	 * @throws InterruptedException 
	 */
	protected List<CalendarEntry> getAllCalendars (String cid) throws IOException, ServiceException, InterruptedException
	{
		if (_allCalendars != null)
		{
			return new ArrayList<CalendarEntry> (_allCalendars);
		}
		
		CalendarFeed resultFeed = null;
		try {
			resultFeed = this.myService.getFeed(this.allcalendarsFeed, CalendarFeed.class);
		} catch (Exception e)
		{
			// second try
			Thread.sleep(5000);
			login();
			resultFeed = this.myService.getFeed(this.allcalendarsFeed, CalendarFeed.class);
		}
		_allCalendars = resultFeed.getEntries();
		
		// filter calendars for this customer
		int i = _allCalendars.size();
		while (i>0)
		{
			i--;
			if (_allCalendars.get(i).getSummary() == null)
			{
				_allCalendars.remove(i);
			} else
			{
				String t = _allCalendars.get(i).getSummary().getPlainText();
				if (t.indexOf("[CID:"+ cid +"]") == -1)
				{
					// not for this customer
					_allCalendars.remove(i);
				}
			}
		}
		
		return _allCalendars;
	}
	
	/**
	 * Adds the entry.
	 * 
	 * @param cal CalendarEntry
	 * @param start DateTime
	 * @param end DateTime
	 * @param title String
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws ServiceException the service exception
	 * 
	 * @return void
	 */
	protected void addEntry(CalendarEntry cal, When eventTime, String title) throws Exception 
	{		
		CalendarEventEntry myEvent = new CalendarEventEntry();
		
		myEvent.setTitle(new PlainTextConstruct(title));
		myEvent.setContent(new PlainTextConstruct("by etApps"));
		
		myEvent.addTime(eventTime);
		
		// get the feed url
		String u = cal.getId().substring(cal.getId().lastIndexOf('/'));
		URL feed = new URL("http://www.google.com/calendar/feeds"+ u +"/private/full");
		
		try {
			this.myService.insert(feed, myEvent);
		} catch (Exception e)
		{
			Thread.sleep(5000);
			this.myService.insert(feed, myEvent);
		}
	}
	
	/**
	 * Use query. Returns CalendarEntries.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws ServiceException the service exception
	 * 
	 * @return void
	 */
	protected List<CalendarEventEntry> getEntries(CalendarEntry cal, String query) throws Exception 
	{
		return getEntries(cal, query, null, null);
	}
	
	protected List<CalendarEventEntry> getEntries(String cal_url, String query) throws Exception
	{
		return getEntries(cal_url, query, null, null);
	}
	
	protected List<CalendarEventEntry> getEntries(CalendarEntry cal, String query, DateTime from, DateTime until) throws Exception 
	{
		// get the feed url
		String u = cal.getId().substring(cal.getId().lastIndexOf('/'));
		return getEntries("http://www.google.com/calendar/feeds"+ u +"/private/full", query, from, until);
	}
	
	protected List<CalendarEventEntry> getEntries(String cal_url, String query, DateTime from, DateTime until) throws Exception 
	{
		URL feed = new URL(cal_url);
		
		CalendarQuery myQuery = new CalendarQuery(feed);
		myQuery.setMaxResults(1000);
		if (query != null)
		{
			myQuery.setFullTextQuery(query);
		}
		
		/*
		 * workaround for google bug:
		 * query.setMaximumStartTime/setMaximumEndTime don't work correctly.
		 * We have to shift the start/end time and filter the time on out own.
		 */
		if (from != null)
		{
			DateTime _f = new DateTime(from.getValue() - 24*60*60*1000);
			myQuery.setMinimumStartTime(_f);
		}
		if (until != null)
		{
			DateTime _u = new DateTime(until.getValue() + 24*60*60*1000);
			myQuery.setMaximumStartTime(_u);
		}
		
		CalendarEventFeed myResultsFeed = null;
		try {
			myResultsFeed = this.myService.query(myQuery, CalendarEventFeed.class);
		} catch (Exception e)
		{
			// wait and try again
			Thread.sleep(3000);
			login();
			myResultsFeed = this.myService.query(myQuery, CalendarEventFeed.class);
		}
		
		if (from != null && until != null)
		{
			int i=0;
			List<CalendarEventEntry> list = myResultsFeed.getEntries();
			CalendarEventEntry e = null;
			while (i<list.size() && (e = list.get(i)) != null)
			{
				if (e.getTimes().size()==0)
				{
					list.remove(i);
				} else
				{
					long start = e.getTimes().get(0).getStartTime().getValue();
					if (
						start < from.getValue()
						|| start > until.getValue()
					) {
						// not in span of time
						list.remove(i);
					} else
					{
						i++; // next entry
					}
				}
			}
			return list;
		} else
		{
			return myResultsFeed.getEntries();
		}
	}
	
	/**
	 * Creates a calendar.
	 * 
	 * @attention this function can takes a long time (up to 2*(3*2000 + 1000) => 14 seconds)
	 * 
	 * @param String title Title of the Calendar
	 * @param String cid Customer ID for identification. This ID is stored in the summary.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws ServiceException the service exception
	 * @throws InterruptedException
	 * 
	 *  @return void
	 */
	@SuppressWarnings("static-access")
	protected CalendarEntry createCalendar(String title, String cid) throws Exception
	{
		for (int i=0; i<2; i++) // max. x retries TODO the retries should be configurable
		{
			CalendarEntry calendar = new CalendarEntry();
			calendar.setTitle(new PlainTextConstruct(title));
			calendar.setSummary(new PlainTextConstruct("created by etApps leave manager [CID:"+ cid +"]"));
			
			URL postURL = new URL("http://www.google.com/calendar/feeds/default/owncalendars/full");
			CalendarEntry returnedCalendar = this.myService.insert(postURL, calendar);
			
			if (!returnedCalendar.getTitle().getPlainText().equals(title))
			{// The calendar's title is incorrect
				// try to set title explicit for 3 times
				for (int ii=0; ii<3; ii++)
				{
					returnedCalendar.update();
					returnedCalendar.setTitle(TextConstruct.create(TextConstruct.Type.TEXT, title, null));
					Thread.currentThread().sleep(1000);
					returnedCalendar.update();
					Thread.currentThread().sleep(1000);
					if (returnedCalendar.getTitle().getPlainText().equals(title))
					{
						return returnedCalendar;
					}
				}
				// if it didn't help, remove and try it again
				returnedCalendar.delete();
				if (i > 0)
				{
					Thread.currentThread().sleep(1000);
				}
			} else
			{
				if (_allCalendars != null)
				{
					_allCalendars.add(returnedCalendar);
				}
				return returnedCalendar;
			}
		}
		throw new Exception("error: Title of calendar couldn't be set correctly.");
	}
	
	/**
	 * Add a user access to calendar
	 * @param cal
	 * @param account
	 */
	protected void addUserAccess(CalendarEntry cal, String account, AclRole role) throws Exception
	{
		AclEntry entry = new AclEntry();
		entry.setScope(new AclScope(AclScope.Type.USER, account));
		entry.setRole(role);
		
		// get the acl Url
		String u = cal.getId().substring(cal.getId().lastIndexOf('/'));
		
		URL aclUrl = new URL("http://www.google.com/calendar/feeds"+ u +"/acl/full");
		try {
			myService.insert(aclUrl, entry);
		} catch (com.google.gdata.util.VersionConflictException e)
		{
			// user access already exists, just update user's role
			entry = getUserAccessEntry(cal, account);
			entry.setRole(role);
			entry.update();
		}
		
		cal.update();
	}

	protected AclEntry getUserAccessEntry(CalendarEntry cal, String account) throws Exception
	{
		// check permission
		Link link = cal.getLink(AclNamespace.LINK_REL_ACCESS_CONTROL_LIST, Link.Type.ATOM);
		// For each calendar, retrieve its ACL feed.
		AclFeed aclFeed = null;
		try {
			aclFeed = myService.getFeed(new URL(link.getHref()), AclFeed.class);
		} catch (Exception e)
		{
			 // retry
			Thread.sleep(3000);
			aclFeed = myService.getFeed(new URL(link.getHref()), AclFeed.class);
		}
		for(AclEntry aclEntry : aclFeed.getEntries())
		{
			if (aclEntry.getScope().getValue().equals( account ))
			{
				return aclEntry;
			}
		}
		return null;
	}
	
	protected boolean checkUserAccess(CalendarEntry cal, String account) throws Exception
	{
		if (getUserAccessEntry(cal, account) == null)
		{
			return false;
		} else
		{
			return true;
		}
	}
	
	/**
	 * Delete calendars.
	 * 
	 * @throws IOException Signals that an I/O exception has occurred.
	 * @throws ServiceException the service exception
	 * 
	 * @return void
	 */
	public void deleteCalendars() throws IOException, ServiceException
	{
		URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/owncalendars/full");
		CalendarFeed resultFeed = this.myService.getFeed(feedUrl, CalendarFeed.class);
		for (int i = 0; i < resultFeed.getEntries().size(); i++) 
		{
			CalendarEntry entry = resultFeed.getEntries().get(i);
			String title = entry.getTitle().getPlainText();
			String summary = entry.getSummary().getPlainText();
			
			if (summary.equals("Dieser Kalender wurde über JAVA API erstellt"))
			{
				System.out.println("Lösche Kalender: " + title);
				try {
					entry.delete();
				} catch (InvalidEntryException e) {
					System.out.println("\tDer Standardkalender kann nicht gelöscht werden");
				}
			}
		}
	}
	
	/**
	 * delete event entry
	 * 
	 * @param entry
	 * @throws Exception
	 */
	protected void deleteEntry(CalendarEventEntry entry) throws Exception
	{
		Exception e = null;
		for (int i=0; i<2; i++)
		{
			try {
				entry.delete();
				return;
			} catch (Exception _e)
			{
				Thread.sleep(1500); // wait
				e = _e;
			}
		}
		throw new EgoException(EgoException.DELETE_HOLIDAY_FAILED, e);
	}
	
	/**
	 * returns the event feed for a employee
	 * 
	 * @param mail
	 * @return URL
	 */
	static protected URL getEventFeedByMail(String mail) throws Exception
	{
		return new URL("http://www.google.com/calendar/feeds/"+mail+"/private/full");
	}
	
	protected void updateEntry(CalendarEventEntry entry) throws Exception
	{
		URL editUrl = new URL(entry.getEditLink().getHref());
		myService.update(editUrl, entry);
	}
	
	public void moveCalendar(CalendarEntry cal, String to) throws Exception
	{
		if (cal == null)
		{
			return;
		}
		
		// set new access permission
		addUserAccess(cal, to, CalendarAclRole.OWNER);
		
		// unsubscribe calendar
		String u = cal.getId().substring(cal.getId().lastIndexOf('/'));
		String url = "http://www.google.com/calendar/feeds/default/allcalendars/full"+u;
		CalendarEntry x = myService.getEntry(new URL(url), CalendarEntry.class);
		x.delete();
	}
}

